package br.com.vantagensaki.utils.generics.impl;

import br.com.vantagensaki.utils.exception.InfraestruturaException;
import br.com.vantagensaki.utils.exception.ObjetoNaoEncontradoException;
import br.com.vantagensaki.utils.generics.DaoGenerico;
import br.com.vantagensaki.utils.generics.ExecutorDeBuscas;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class HibernateDaoGenerico<T, PK extends Serializable>
        extends HibernateDaoSupport
        implements DaoGenerico<T, PK>, ExecutorDeBuscas {

    private Class<T> tipo;

    public HibernateDaoGenerico(Class<T> tipo) {
        this.tipo = tipo;
    }

    public T inclui(T o) {
        HibernateTemplate ht = getHibernateTemplate();
        try {
            ht.save(o);
        } catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        return o;
    }

    public T getPorId(PK id) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();
        T t = null;
        try {
            t = (T) ht.get(tipo, id);
        } catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (t == null) {
            throw new ObjetoNaoEncontradoException();
        }
        return t;
    }

    public T getPorIdComLock(PK id) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();
        T t = null;
        try {
            t = (T) ht.get(tipo, id);
        } catch (Exception e) {
            throw new InfraestruturaException(e);
        }

        if (t == null) {
            throw new ObjetoNaoEncontradoException();
        }

        ht.lock(t, LockMode.UPGRADE);

        return t;
    }

    public void altera(T o) {
        HibernateTemplate ht = getHibernateTemplate();
        try {
            ht.merge(o);
        } catch (Exception e) {
            throw new InfraestruturaException(e);
        }
    }

    public void exclui(T o) {
        HibernateTemplate ht = getHibernateTemplate();
        try {
            ht.delete(o);
        } catch (Exception e) {
            throw new InfraestruturaException(e);
        }
    }

    public T busca(Method metodo, Object[] argumentos, String nomeQuery) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();

        List<T> list = null;
        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);
            list = ht.findByNamedQuery(nomeDaBusca, argumentos);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (list == null || list.isEmpty()) {
            throw new ObjetoNaoEncontradoException();
        }

        return (T) list.get(0);
    }

    public T buscaParametrizada(Method metodo, Object[] argumentos, String nomeQuery) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();

        List<T> list = null;
        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            String [] args = montaArrayArgumentosHQL(argumentos);

            list = ht.findByNamedQueryAndNamedParam(nomeDaBusca, args, argumentos);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (list == null || list.isEmpty()) {
            throw new ObjetoNaoEncontradoException();
        }

        return (T) list.get(0);
    }

    public T buscaPrimeiro(Method metodo, Object[] argumentos, String nomeQuery) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();

        List<T> list = null;
        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            list = ht.findByNamedQuery(nomeDaBusca, argumentos);
        } catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (list == null || list.isEmpty()) {
            throw new ObjetoNaoEncontradoException();
        }

        return (T) list.get(0);
    }

    public T buscaPrimeiroParametrizada(Method metodo, Object[] argumentos, String nomeQuery) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();

        List<T> list = null;
        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            String [] args = montaArrayArgumentosHQL(argumentos);

            list = ht.findByNamedQueryAndNamedParam(nomeDaBusca, args, argumentos);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (list == null || list.isEmpty()) {
            throw new ObjetoNaoEncontradoException();
        }

        return (T) list.get(0);
    }

    public T buscaUltimo(Method metodo, Object[] argumentos, String nomeQuery) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();

        List<T> list = null;
        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);
            list = ht.findByNamedQuery(nomeDaBusca, argumentos);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (list == null || list.isEmpty()) {
            throw new ObjetoNaoEncontradoException();
        }

        return (T) list.get(list.size() - 1);
    }

    public T buscaUltimoParametrizada(Method metodo, Object[] argumentos, String nomeQuery) throws ObjetoNaoEncontradoException {
        HibernateTemplate ht = getHibernateTemplate();

        List<T> list = null;
        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            String [] args = montaArrayArgumentosHQL(argumentos);

            list = ht.findByNamedQueryAndNamedParam(nomeDaBusca, args, argumentos);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
        if (list == null || list.isEmpty()) {
            throw new ObjetoNaoEncontradoException();
        }

        return (T) list.get(list.size() - 1);
    }

    public List<T> buscaLista(Method metodo, Object[] argumentos, String nomeQuery) {
        HibernateTemplate ht = getHibernateTemplate();

        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            List<T> list = ht.findByNamedQuery(nomeDaBusca, argumentos);

            return list;
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
    }

    public List<T> buscaListaParametrizada(Method metodo, Object[] argumentos, String nomeQuery) {
        HibernateTemplate ht = getHibernateTemplate();

        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            String [] args = montaArrayArgumentosHQL(argumentos);

            List<T> list = ht.findByNamedQueryAndNamedParam(nomeDaBusca, args, argumentos);

            return list;
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }
    }

    public Set<T> buscaConjunto(Method metodo, Object[] argumentos, String nomeQuery) {
        HibernateTemplate ht = getHibernateTemplate();

        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            List<T> list = ht.findByNamedQuery(nomeDaBusca, argumentos);

            return new LinkedHashSet(list);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }

    }

    public Set<T> buscaConjuntoParametrizada(Method metodo, Object[] argumentos, String nomeQuery) {
        HibernateTemplate ht = getHibernateTemplate();

        try {
            String nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);

            String [] args = montaArrayArgumentosHQL(argumentos);

            List<T> list = ht.findByNamedQueryAndNamedParam(nomeDaBusca, args, argumentos);
            
            return new LinkedHashSet(list);
        }
        catch (Exception e) {
            throw new InfraestruturaException(e);
        }

    }

    public String getNomeDaBuscaPeloMetodoENomeQuery(Method metodo, String nomeQuery) {
        if (nomeQuery != null && !nomeQuery.equals("")) {
            return tipo.getSimpleName() + "." + nomeQuery;
        }
        return tipo.getSimpleName() + "." + metodo.getName();
    }

    private String[] montaArrayArgumentosHQL(Object[] argumentos) {
        String [] args = new String[argumentos.length];
        for (int i = 0; i < argumentos.length; i++) {
            args[i] = "$" + (i + 1);
        }
        return args;
    }

    public List buscaListaPaginada(Method metodo,
            Object[] argumentos, String nomeQuery, String nomeQueryCount, int pageSize) {
        HibernateTemplate ht = getHibernateTemplate();

//        String nomeDaBusca = null;
//
//        try {
//
//            nomeDaBusca = getNomeDaBuscaPeloMetodoENomeQuery(metodo, nomeQuery);
//
//            String nomeDoCount = nomeDaBusca + "Count";
//
//            if (nomeQueryCount != null && !nomeQueryCount.equals("")) {
//                nomeDoCount = tipo.getSimpleName() + "." + nomeQueryCount;
//            }
//
//            List<T> list = ht.findByNamedQuery(nomeDoCount, argumentos);
//            long tamanhoTotal = (Long) list.get(0);
//
//            return new HibernateListaComPaginacao(nomeDaBusca, argumentos, pageSize, tamanhoTotal, true);
//
//        } catch (PersistenceException pe) {
//            throw new InfraestruturaException("A NamedQuery do Count deve ser criada. O seu nome deve ser: " + nomeDaBusca + "Count", pe);
//        } catch (Exception e) {
//            throw new InfraestruturaException(e);
//        }

        return null;
    }

    public static <T> List<T> executaQuery(String hql, Object[] parametros, int inicio, int quantidade, boolean namedQuery) {

//        Session sessao = ((SessionFactory) AppContext.getApplicationContext().getBean("sessionFactory")).openSession();
        
        ServletContext servletContext = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
        ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        Session sessao = ((SessionFactory) appContext.getBean("sessionFactory")).openSession();
        
        try {
            Query query = null;
            if (namedQuery) {
                query = sessao.getNamedQuery(hql);
            } else {
                query = sessao.createQuery(hql);
            }

            for (int i = 0; i < parametros.length; i++) {
                Object arg = parametros[i];
                query.setParameter(i, arg);
            }
            return query.setFirstResult(inicio).setMaxResults(quantidade).list();

        } catch (Exception e) {
            throw new InfraestruturaException(e);
        } finally {
            sessao.close();
        }
    }

}
